import tkinter as tk  # 导入tkinter模块用于创建GUI应用
from tkinter import filedialog  # 导入filedialog用于加载图片
from PIL import Image, ImageTk  # 导入PIL模块进行图像处理，并将其转换为Tkinter可处理格式
import cv2  # 导入OpenCV模块用于图像处理
import numpy as np  # 导入numpy模块进行矩阵运算

import logging  # 导入logging模块记录日志信息

logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', level=logging.INFO)

class App:
    # 初始化GUI(图形用户界面)
    def __init__(self, root):
        self.root = root

        # 定义图像和遮罩变量
        self.img = None  # 定义img变量，用于存储加载的图片
        self.mask = None  # 定义mask变量，用于存储图片的遮罩信息
        self.file_path = None  # 定义存储文件路径的变量
        self.need_refresh = False  # 定义是否需要刷新的标志
        self.current_method = None  # 定义当前选择的算法

        # 创建按钮和画布
        self.create_widgets()  # 调用创建按钮和画布的函数

        # 绑定画笔动作
        self.bind_brush_action()  # 调用绑定画笔动作的函数

        # 初始化其他变量
        self.prev_pts = []  # 存储前一笔画的点
        self.pen_strokes = []  # 存储全部笔画数据

    # 创建载入图片按钮
    def create_widgets(self):
        self.load_button = tk.Button(self.root, text="载入图片", command=self.load_image)  # 创建载入图片按钮，点击后调用load_image函数
        self.load_button.pack()  # 加载按钮到窗口

        # 创建画布框架
        self.image_frame = tk.Frame(self.root)  # 定义一个新的框架存储画布
        self.image_frame.pack()  # 加载新的框架到窗口

        # 创建显示图片的画布
        self.canvas_width = 350  # 定义画布宽度
        self.canvas_height = int(self.canvas_width*0.75)  # 定义画布高度，宽高比为4:3
        self.canvas1 = tk.Canvas(self.image_frame, width=self.canvas_width, height=self.canvas_height)  # 创建显示原图的画布
        self.canvas1.pack(side=tk.LEFT)  # 加载画布到框架
        self.canvas2 = tk.Canvas(self.image_frame, width=self.canvas_width, height=self.canvas_height)  # 创建显示处理后图像的画布
        self.canvas2.pack(side=tk.LEFT)  # 加载画布到框架

        # 创建操作按钮框架
        self.button_frame = tk.Frame(self.root)  # 定义一个新的框架存储操作按钮
        self.button_frame.pack()  # 加载新的框架到窗口

        # 创建FMM和NS按钮
        self.fmm_button = tk.Button(self.button_frame, text="FMM", command=self.apply_fmm)  # 创建FMM算法按钮，点击后调用apply_fmm函数
        self.fmm_button.pack(side=tk.LEFT)  # 加载按钮到框架
        self.ns_button = tk.Button(self.button_frame, text="NS", command=self.apply_ns)  # 创建NS算法按钮，点击后调用apply_ns函数
        self.ns_button.pack(side=tk.LEFT)  # 加载按钮到框架

        # 创建撤销按钮
        self.undo_button = tk.Button(self.button_frame, text="Undo", command=self.undo)  # 创建撤销按钮，点击后调用undo函数
        self.undo_button.pack(side=tk.LEFT)  # 加载按钮到框架

        # 创建刷新按钮
        self.refresh_button = tk.Button(self.button_frame, text="Refresh", command=self.refresh)  # 创建刷新按钮，点击后调用refresh函数
        self.refresh_button.pack(side=tk.LEFT)  # 加载按钮到框架
        
        # 创建滑动条
        self.slider = tk.Scale(self.root, from_=3, to=51, orient=tk.HORIZONTAL, length=200, resolution=2,
                            command=self.slider_update, label="窗口大小")  # 创建调整窗口大小的滑动条，滑动后调用slider_update函数
        self.slider.set(7)  # 设置滑动条默认值为7
        self.slider.pack()  # 加载滑动条到窗口

    # 绑定画笔动作
    def bind_brush_action(self):
        self.canvas1.bind("<B1-Motion>", self.draw_line)  # 绑定左键拖动事件，调用draw_line函数
        self.canvas1.bind("<ButtonRelease-1>", self.reset_prev_point)  # 绑定左键释放事件，调用reset_prev_point函数

 # 载入图像和遮罩, 确保图像的高度和宽度按照画布的尺寸进行调节
    def load_img_mask(self):
        logging.info(self.file_path)  # 输出文件路径到日志
        self.img = cv2.imread(self.file_path)  # 读取图像到img变量
        logging.info(self.img.shape)  # 输出图像尺寸到日志
        self.mask = np.zeros(self.img.shape[:2], np.uint8)  # 创建和图像大小相同的遮罩
        h, w = self.img.shape[:2]  # 获取图像高度和宽度

        # 缩放图像和遮罩
        scale = min(self.canvas_width / w, self.canvas_height / h)
        new_w = int(w * scale)
        new_h = int(h * scale)
        self.img = cv2.resize(self.img, (new_w, new_h))
        self.mask = cv2.resize(self.mask, (new_w, new_h))

    # 加载图像
    def load_image(self):
        self.file_path = filedialog.askopenfilename()
        if self.file_path:
            self.load_img_mask()
            self.show_image()

    # 显示图像
    def show_image(self):
        img_rgb = cv2.cvtColor(self.img, cv2.COLOR_BGR2RGB)
        img_pil = Image.fromarray(img_rgb)
        img_tk = ImageTk.PhotoImage(img_pil)
        self.canvas1.create_image(0, 0, anchor=tk.NW, image=img_tk)
        self.canvas1.image = img_tk

    # 画线
    def draw_line(self, event):
        if self.prev_pts:
            x, y = event.x, event.y
            x_prev, y_prev = self.prev_pts[-1]
            self.canvas1.create_line(x_prev, y_prev, x, y, fill="yellow", width=3)
            cv2.line(self.mask, (x_prev, y_prev), (x, y), 255, 5)
            self.prev_pts.append((x, y))
        else:
            self.prev_pts.append((event.x, event.y))

    # 重置前一个点
    def reset_prev_point(self, event):
        self.prev_pts = []

    # 更新滑动条
    def slider_update(self, value):
        self.window_size = int(value)
        logging.info(f"Window size updated to: {self.window_size}")

    # 应用FMM算法
    def apply_fmm(self):
        if self.img is not None and self.mask is not None:
            self.current_method = 'FMM'
            self.inpaint()

    # 应用NS算法
    def apply_ns(self):
        if self.img is not None and self.mask is not None:
            self.current_method = 'NS'
            self.inpaint()

    # 图像修复
    def inpaint(self):
        if self.current_method == 'FMM':
            inpainted_img = cv2.inpaint(self.img, self.mask, self.window_size, cv2.INPAINT_TELEA)
        else:
            inpainted_img = cv2.inpaint(self.img, self.mask, self.window_size, cv2.INPAINT_NS)

        img_rgb = cv2.cvtColor(inpainted_img, cv2.COLOR_BGR2RGB)
        img_pil = Image.fromarray(img_rgb)
        img_tk = ImageTk.PhotoImage(img_pil)
        self.canvas2.create_image(0, 0, anchor=tk.NW, image=img_tk)
        self.canvas2.image = img_tk

    # 撤销最后一笔
    def undo(self):
        if self.pen_strokes:
            self.pen_strokes.pop()
            self.refresh()

    # 刷新画布
    def refresh(self):
        self.need_refresh = True
        self.mask = np.zeros(self.img.shape[:2], np.uint8)
        for stroke in self.pen_strokes:
            for i in range(len(stroke) - 1):
                cv2.line(self.mask, stroke[i], stroke[i+1], 255, 5)
        self.show_image()

if __name__ == "__main__":
    root = tk.Tk()
    app = App(root)
    root.mainloop()